// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html

// import SuperListItem from "./SuperListItem";

interface SuperListParam {
    layoutType: string,     //布局类型
    spacingX?: number,      //item的x间隔
    spacingY?: number,      //item的y间隔
    top?: number,           //上边距
    bottom?: number,        //下边距
    left?: number,          //左边距
    right?: number,         //右边距
    cacheNum?: number,      //多缓存的item数量
    durationMs?: number,    //分帧时间间隔
}

const SuperLayoutType = {
    Vertical: "vertical",
    Horizontal: "horizontal",
    Grid: "grid"
}

const { ccclass, property } = cc._decorator;

@ccclass
export default class SuperListView extends cc.Component {

    /************************************************输入参数begin************************************************/
    // 预制体模板
    private pfTemplate: cc.Prefab = null;

    // item之间的横向间隙
    private spacingX: number = 0;

    // item之间的纵向间隙
    private spacingY: number = 0;

    //item离父容器的顶部距离
    private top: number = 0;

    //item离父容器的底部的距离
    private bottom: number = 0;

    //item离父容器的左部的距离
    private left: number = 0;

    //item离父容器的右部的距离
    private right: number = 0;

    // ScrollView组件
    private scrollView: cc.ScrollView = null;

    // 布局类型
    private layoutType: string = SuperLayoutType.Vertical;

    //比可视Item数 多缓存的数目
    private cacheNum: number = 2;

    // 输入的item对应的数据
    private itemDataList: any[] = [];

    // 分帧时间间隔 单位为ms
    private durationMs: number = 1;

    // 每横排（纵列）最多有多少个item
    private spawnItemNum: number = 1;

    /************************************************输入参数end************************************************/

    // item节点池
    private itemNodePool: cc.NodePool = null;

    // item的容器节点
    private content: cc.Node = null;

    // 在列表中可视Item的个数
    private visibleItemNum: number = 0;

    // item的width
    private itemWidth: number = 0;

    // item的height
    private itemHeight: number = 0;

    // 上一次的content坐标
    private oldContentPos: { x: number, y: number } = { x: 0, y: 0 };

    // 记录当前填充在content容器的Item的索引. 用来快速查找哪些位置缺少item了.
    private readonly _filledIndexs: { [key: number]: number } = {};

    private scrollBottomNotifyed: boolean = false;

    private lastStartIndex: number = -1;

    private pullDownCallback: () => void = null;

    private pullUpCallback: () => void = null;

    /**
     * 
     * @param node 将本组件绑定到那个节点
     * @param scrollView ScrolView组件
     * @param pfTemplate item预制模板
     * @param param 输入的一系列参数 SuperListParam
     */
    public init(node: cc.Node, scrollView: cc.ScrollView, pfTemplate: cc.Prefab, param: SuperListParam) {
        this.node = node;
        this.scrollView = scrollView;
        this.scrollView.elastic = false;
        this.pfTemplate = pfTemplate;

        this.spacingX = param.spacingX || 0;
        this.spacingY = param.spacingY || 0;
        this.top = param.top || 0;
        this.bottom = param.bottom || 0;
        this.left = param.left || 0;
        this.right = param.right || 0;
        this.layoutType = (param.layoutType).toLowerCase() || SuperLayoutType.Vertical;
        this.cacheNum = param.cacheNum || 2;
        this.durationMs = param.durationMs || 1;

        this.content = this.scrollView.content;

        console.log("layoutType:", this.layoutType);

        this.initNodePool();
        this.initEvent()
    }

    private initEvent() {
        this.scrollView.node.on("scrolling", this.onScrolling.bind(this));
    }

    onDestroy() {
        this.scrollView.node.off("scrolling", this.onScrolling.bind(this));
    }

    /**
     * 
     * @param dataList item的依赖数据
     * @param cb 页面显示完毕后  回调
     */
    public setData(dataList: any[], cb?: Function) {
        if (this.scrollView.isAutoScrolling() || this.scrollView.isScrolling()) {
            this.scrollView.stopAutoScroll();
        }

        this.itemDataList = dataList;
        this.notifyUpdate();

        if (this.scrollView.horizontal) {
            this.scrollView.scrollToLeft();
        } else {
            this.scrollView.scrollToTop();
        }

        this.scheduleOnce(() => {
            cb && cb();
        }, 0)

    }

    /**
     * 
     * @param index  滑动到制定index的item
     * @param seconds 滑动耗时 单位：秒
     */
    public scrollToIndex(index: number, seconds: number = 0) {
        cc.log("index:", index);
        let count = this.itemDataList.length;
        cc.log("childCount:", count);
        if (index < 1) {
            cc.log("index 过小");
            index = 1;
        }
        else if (index > count) {
            cc.log("index 过大");
            index = count;
        }

        if (this.layoutType === SuperLayoutType.Vertical) {
            if (index == 1) {
                this.scrollView.scrollToTop(seconds);
            } else if (index == count) {
                this.scrollView.scrollToBottom(seconds);
            } else {
                this.scrollView.scrollToPercentVertical(index / count, seconds);
            }
        } else if (this.layoutType === SuperLayoutType.Horizontal) {
            if (index == 1) {
                this.scrollView.scrollToLeft(seconds);
            } else if (index == count) {
                this.scrollView.scrollToRight(seconds);
            } else {
                this.scrollView.scrollToPercentHorizontal(index / count, seconds);
            }
        } else if (this.layoutType === SuperLayoutType.Grid) {

            if (this.scrollView.vertical) {
                let hang = Math.ceil(index / this.spawnItemNum);
                let totalHang = Math.ceil(count / this.spawnItemNum);
                this.scrollView.scrollToPercentVertical(hang / totalHang, seconds);
            } else {
                let lie = Math.ceil(index / this.spawnItemNum);
                let totalLie = Math.ceil(count / this.spawnItemNum);
                this.scrollView.scrollToPercentHorizontal(lie / totalLie, seconds);
            }

        } else {
            console.log("this.layoutType 未设置");
        }
    }

    public scrollToBottom(anim: boolean = false) {
        this.scrollView.scrollToBottom(anim ? 1 : 0);
    }

    public scrollToRight(anim: boolean = false) {
        this.scrollView.scrollToRight(anim ? 1 : 0);
    }

    public scrollToTop(anim: boolean = false) {
        this.scrollView.scrollToTop(anim ? 1 : 0);
    }

    public scrollToLeft(anim: boolean = false) {
        this.scrollView.scrollToLeft(anim ? 1 : 0);
    }

    public getContent(): cc.Node {
        return this.content;
    }

    // 下拉事件.
    public pullDown(callback: () => void, this$: any) {
        this.pullDownCallback = callback.bind(this$);
    }

    // 上拉事件.
    public pullUp(callback: () => void, this$: any) {
        this.pullUpCallback = callback.bind(this$);
    }

    private onScrolling() {
        //判断误差
        if (this.scrollView.horizontal) {
            if (Math.abs(this.content.x - this.oldContentPos.x) < 10) return;
            this.oldContentPos.x = this.content.x;
        } else {
            if (Math.abs(this.content.y - this.oldContentPos.y) < 10) return;
            this.oldContentPos.y = this.content.y;
        }

        //恢复item到原来宽或者高
        if (this.layoutType != SuperLayoutType.Grid) {
            this.updateItemsToDefault();
        }

        this.upDataLayout()

        //检查滚动item
        const startIndex = this.checkNeedUpdate();
        //index改变了去更新
        if (startIndex >= 0) {
            this.updateView(startIndex);
        }
    }

    // 将item恢复到默认状态
    private updateItemsToDefault() {
        if (this.layoutType == SuperLayoutType.Vertical) {

            this.content.children.forEach((node) => {

                if (node.width !== this.itemHeight) {
                    let pfListItem = node.getComponent('SuperListItem');
                    if (pfListItem && pfListItem.defaultForm) {
                        pfListItem.defaultForm();
                    }
                }

            })
        } else if (this.layoutType == SuperLayoutType.Horizontal) {
            this.content.children.forEach((node) => {

                if (node.height !== this.itemWidth) {
                    let pfListItem = node.getComponent('SuperListItem');
                    if (pfListItem && pfListItem.defaultForm) {
                        pfListItem.defaultForm();
                    }
                }

            })
        }
    }

    // 填充View.
    private updateView(startIndex) {

        //实际显示元素第一个元素的下标
        let itemStartIndex = startIndex;
        // 比实际元素多2个. 实际显示元素最后一个元素的下标 去除自己
        let itemEndIndex = itemStartIndex + (this.visibleItemNum - 1) + this.cacheNum;
        if (this.layoutType == SuperLayoutType.Grid) {
            itemEndIndex = itemStartIndex + (this.visibleItemNum - 1) + this.spawnItemNum;
        }
        //获取所有的元素个数
        const totalCount = this.itemDataList.length;

        if (itemStartIndex >= totalCount) {
            return;
        }

        if (itemEndIndex >= totalCount) {

            itemEndIndex = totalCount - 1;
            //拖拽到底了
            if (itemStartIndex > 0 && (!this.scrollBottomNotifyed)) {
                this.notifyScrollToBottom()
                this.scrollBottomNotifyed = true;
            }
        } else {
            this.scrollBottomNotifyed = false;
        }

        // 回收需要回收的元素位置，
        const recyles = this.getRecycleItems(itemStartIndex, itemEndIndex);
        recyles.forEach(item => {
            this.itemNodePool.put(item);
        })

        // 查找需要更新的元素位置.
        const updates = this.findUpdateIndex(itemStartIndex, itemEndIndex)

        //分帧加载
        this.executePreFrame(this._getGenerator(updates), this.durationMs, () => {
            this.upDataLayout();
        });

    }

    // 查找需要补充的元素索引.
    private findUpdateIndex(itemStartIndex: number, itemEndIndex: number): number[] {

        const d = [];
        for (let i = itemStartIndex; i <= itemEndIndex; i++) {
            if (this._filledIndexs.hasOwnProperty(i)) {
                continue;
            }
            d.push(i);
        }
        return d;
    }

    // 获取可回收item
    private getRecycleItems(beginIndex: number, endIndex: number): cc.Node[] {

        const children = this.content.children;
        const recycles = []
        children.forEach(item => {
            if (item["_tag"] < beginIndex || item["_tag"] > endIndex) {
                recycles.push(item);

                delete this._filledIndexs[item["_tag"]];
            }

        })
        return recycles;
    }

    private notifyScrollToBottom() {
        if (this.itemDataList.length <= 0) {
            return;
        }

        if (this.pullUpCallback) {
            this.pullUpCallback();
        }
    }

    private upDataLayout() {
        if (this.layoutType == SuperLayoutType.Grid) {
            return;
        }

        //获取真实第一个children
        if (this.content.children.length <= 0) return;
        if (this.layoutType === SuperLayoutType.Horizontal) {

            let width = this.content.children[0].width + this.spacingX;
            for (let i = 1; i < this.content.children.length; i++) {
                this.content.children[i].x = this.content.children[i - 1].x + this.content.children[i - 1].width + this.spacingX;
                width += this.content.children[i].width + this.spacingX;
            }

            this.content.width = width + this.left + this.right + (this.itemDataList.length - this.content.children.length) * (this.itemWidth + this.spacingX);

            //判断最后一个是否打开
            if (this.content.children.length >= this.visibleItemNum && this.content.children[this.content.children.length - 1].width != this.itemWidth) {
                this.scrollToRight();
            }
        } else if (this.layoutType === SuperLayoutType.Vertical) {

            let height = this.content.children[0].height + this.spacingY;

            for (let i = 1; i < this.content.children.length; i++) {
                this.content.children[i].y = this.content.children[i - 1].y - this.content.children[i - 1].height - this.spacingY;
                height += this.content.children[i].height + this.spacingY;
            }

            this.content.height = height + this.top + this.bottom + (this.itemDataList.length - this.content.children.length) * (this.itemHeight + this.spacingY);
            //判断最后一个是否打开 
            if (this.content.children.length >= this.visibleItemNum && this.content.children[this.content.children.length - 1].height != this.itemHeight) {
                this.scrollToBottom();
            }
        }
    }

    // 检测是否需要更新UI.
    private checkNeedUpdate(): number {

        if (this.itemDataList.length <= 0) {
            return -1;
        }

        let scroll = 0;
        let itemStartIndex = 0;

        if (this.scrollView.horizontal) {
            scroll = -this.content.x - this.content.getParent().width * this.content.getParent().anchorX;
            itemStartIndex = Math.floor(scroll / (this.itemWidth + this.spacingX)) * this.spawnItemNum;
        } else {
            scroll = this.content.y - this.content.getParent().height * this.content.getParent().anchorY;
            itemStartIndex = Math.floor(scroll / (this.itemHeight + this.spacingY)) * this.spawnItemNum;
        }


        if (this.lastStartIndex != itemStartIndex) {

            this.lastStartIndex = itemStartIndex;

            return itemStartIndex;
        }

        return -1;
    }

    private notifyUpdate() {
        if (this.itemDataList.length <= 0) {
            return;
        }

        Object.keys(this._filledIndexs).forEach(key => {
            delete this._filledIndexs[key];
        })

        this.recycleAll();

        this.lastStartIndex = -1;

        if (this.scrollView.horizontal) {
            this.content.width = Math.ceil(this.itemDataList.length / this.spawnItemNum) * (this.itemWidth + this.spacingX) - this.spacingX + this.left + this.right;
        } else {
            this.content.height = Math.ceil(this.itemDataList.length / this.spawnItemNum) * (this.itemHeight + this.spacingY) - this.spacingY + this.top + this.bottom; // get total content height
        }

        this.scrollView.scrollToTop();

        //画出所有要显示的UI 从0开始
        this.updateView(0);

        this.upDataLayout();
    }

    private recycleAll() {
        const children = this.content.children;
        children.forEach(item => {
            this.itemNodePool.put(item);
        })
        this.content.removeAllChildren();
    }

    // 初始化对象池
    private initNodePool() {

        let itemNode: cc.Node = cc.instantiate(this.pfTemplate);
        this.itemWidth = itemNode.width;
        this.itemHeight = itemNode.height;


        this.spawnItemNum = 1;
        if (this.layoutType == SuperLayoutType.Vertical) {
            this.scrollView.horizontal = false;
            this.scrollView.vertical = true;

            this.content.anchorX = 0.5;
            this.content.anchorY = 1;

            // 校准位置
            this.content.y = this.content.getParent().height * this.content.getParent().anchorY;

            // 计算可视item数
            this.visibleItemNum = Math.ceil((this.content.getParent().height - this.top - this.bottom - this.spacingY) / (this.itemHeight + this.spacingY)) + 1;
        } else if (this.layoutType == SuperLayoutType.Horizontal) {
            this.scrollView.horizontal = true;
            this.scrollView.vertical = false;

            this.content.anchorX = 0;
            this.content.anchorY = 0.5;

            this.content.x = 0;

            this.visibleItemNum = Math.ceil((this.content.getParent().width - this.left - this.right - this.spacingX) / (this.itemWidth + this.spacingX)) + 1;
        } else if (this.layoutType == SuperLayoutType.Grid) {
            if (this.scrollView.vertical) {
                this.scrollView.horizontal = false;
                this.content.anchorX = 0
                this.content.anchorY = 1;
                // 校准位置
                this.content.y = this.content.getParent().height * this.content.getParent().anchorY;

                this.spawnItemNum = Math.floor((this.content.getParent().width - this.left - this.right) / (this.itemWidth + this.spacingX));
                //@ts-ignore
                this.visibleItemNum = (Math.ceil((this.content.getParent().height - this.top - this.bottom) / (this.itemHeight + this.spacingY)) + 1) * this.spawnItemNum;
            } else {
                this.scrollView.horizontal = true;
                this.content.anchorX = 0;
                this.content.anchorY = 1
                this.content.x = 0;

                this.spawnItemNum = Math.floor((this.content.getParent().height - this.top - this.bottom) / (this.itemHeight + this.spacingY));
                //@ts-ignore
                this.visibleItemNum = (Math.ceil((this.content.getParent().width - this.left - this.right) / (this.itemWidth + this.spacingX)) + 1) * this.spawnItemNum;
            }


        } else {
            console.log("layoutType 未设置！！！");
        }

        this.itemNodePool = new cc.NodePool();
        for (let i = 0; i < this.visibleItemNum + this.cacheNum; i++) {
            let itemNode = cc.instantiate(this.pfTemplate);
            this.fixNode(itemNode);
            this.itemNodePool.put(itemNode);
        }

        this.oldContentPos.x = this.content.x;
        this.oldContentPos.y = this.content.y;

        console.log("this.spawnItemNum:", this.spawnItemNum);
        console.log("item节点池数量:", this.itemNodePool.size());
    }


    // 分帧
    //@ts-ignore
    private executePreFrame(generator: Generator, duration: number, cb?: Function) {
        return new Promise((resolve, reject) => {
            let gen = generator;
            // 创建执行函数
            let execute = () => {
                // 执行之前，先记录开始时间
                let startTime = new Date().getTime();

                // 然后一直从 Generator 中获取已经拆分好的代码段出来执行
                for (let iter = gen.next(); ; iter = gen.next()) {
                    // 判断是否已经执行完所有 Generator 的小代码段，如果是的话，那么就表示任务完成
                    if (iter == null || iter.done) {
                        // ge.log("全部执行完毕")
                        resolve("")
                        return;
                    }

                    // 每执行完一段小代码段，都检查一下是否已经超过我们分配的本帧，这些小代码端的最大可执行时间
                    if (new Date().getTime() - startTime > duration) {
                        // 如果超过了，那么本帧就不在执行，开定时器，让下一帧再执行
                        this.scheduleOnce(() => {
                            execute();
                        });
                        return;
                    }
                }
            };

            // 运行执行函数
            execute();
        }).then(() => {
            cb();
        });
    }

    private *_getGenerator(updateIndexs) {
        let arr = Object.keys(this._filledIndexs);
        let fildedLenght = arr.length;
        let lastNumber = parseInt(arr[arr.length - 1])
        for (let index of updateIndexs) {
            yield this._initPrefab(index, fildedLenght, lastNumber);
        }
    }

    private _initPrefab(index: number, fildedLenght, lastNumber) {

        let child = this.getNewNode();
        child.getComponent('SuperListItem').setData(this.itemDataList[index]);

        if (this.scrollView.horizontal) {
            this._layoutHorizontal(child, index, fildedLenght, lastNumber)
        } else {
            this._layoutVertical(child, index, fildedLenght, lastNumber);
        }
    }

    // 向某位置添加一个item.
    private _layoutHorizontal(child: cc.Node, posIndex: number, fildedLenght: number, lastNumber: number) {

        if (fildedLenght > 0 && posIndex < lastNumber) {
            this.content.insertChild(child, 0);
        } else {
            this.content.addChild(child);
        }
        // 增加一个tag 属性用来存储child的位置索引.
        child["_tag"] = posIndex;
        this._filledIndexs[posIndex] = posIndex;


        let positionX = child.width * posIndex + this.spacingX * (posIndex) + this.left;
        let positionY = 0;
        if (this.layoutType == SuperLayoutType.Grid) {
            positionX = this.left + child.width * child.scaleX * Math.floor(posIndex / this.spawnItemNum) + this.spacingX * Math.floor(posIndex / this.spawnItemNum);

            positionY = -child.height * child.scaleY * (posIndex % this.spawnItemNum) - this.spacingY * (posIndex % this.spawnItemNum) - this.top;
        }
        child.setPosition(positionX, positionY);
    }

    // 向某位置添加一个item.
    private _layoutVertical(child: cc.Node, posIndex: number, fildedLenght: number, lastNumber: number) {

        if (fildedLenght > 0 && posIndex < lastNumber) {
            this.content.insertChild(child, 0);
        } else {
            this.content.addChild(child);
        }

        //判断加入进content的数组的位置，如果默认加就是加入到数组的最后影响重新布局

        // 增加一个tag 属性用来存储child的位置索引.
        child["_tag"] = posIndex;
        this._filledIndexs[posIndex] = posIndex;

        let positionX = 0;
        let posititonY = -child.height * posIndex - this.spacingY * posIndex - this.top;

        if (this.layoutType == SuperLayoutType.Grid) {
            posititonY = -child.height * child.scaleY * Math.floor(posIndex / this.spawnItemNum) - this.spacingY * Math.floor(posIndex / this.spawnItemNum) - this.top;

            positionX = this.left + child.width * child.scaleX * (posIndex % this.spawnItemNum) + this.spacingX * (posIndex % this.spawnItemNum)
        }

        child.setPosition(positionX, posititonY);
    }

    private getNewNode() {
        let newNode = this.itemNodePool.get();
        if (!newNode) {
            newNode = cc.instantiate(this.pfTemplate);
            this.fixNode(newNode);
        }
        return newNode;
    }

    private fixNode(node: cc.Node) {

        if (node.anchorX == this.content.anchorX && node.anchorY == this.content.anchorY) {
            return;
        }

        node.anchorX = this.content.anchorX;
        node.anchorY = this.content.anchorY;

        node.children.forEach((child) => {
            if (child.anchorX != node.anchorX) {
                child.x += (child.anchorX - node.anchorX) * node.width;
            }

            if (child.anchorY != node.anchorY) {
                child.y += (child.anchorY - node.anchorY) * node.height;
            }
        })

    }

}
